﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestProject.Assessment
{
    public static class ExceptionAssert
    {
        public static void ExpectException<TException>(Action action) where TException : Exception
        {
            ExpectException<TException>(action, string.Empty);
        }

        public static void ExpectException<TException, TResult>(Func<TResult> action) where TException : Exception
        {
            ExpectException<TException>(() => action(), string.Empty);
        }

        public static void ExpectException<TException>(Action action, string expectedMessagePartial) where TException : Exception
        {
            try
            {
                action();
            }
            catch (TException ex)
            {
                if (ex.GetType() != typeof(TException))
                    throw new AssertFailedException(string.Format("Waited \"{0}\" exception, but \"{1}\" was", typeof(TException), ex.GetType()));
                if (!ex.Message.ToLower().Contains(expectedMessagePartial.ToLower()))
                    throw new AssertFailedException(string.Format("Specified type of exception was thrown but the error message is incorrect. The error message \"{0}\" should contain \"{1}\" string", ex.Message, expectedMessagePartial));
                return;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                throw new AssertFailedException(string.Format("Waited \"{0}\" exception, but \"{1}\" was", typeof(TException), ex.GetType()));
            }
            throw new AssertFailedException(string.Format("Excpected exception \"{0}\" wasn't thrown", typeof(TException)));
        }
    }

    [TestClass]
    public class ExceptionAssertTest
    {
        [TestMethod]
        [ExpectedException(typeof(AssertFailedException))]
        public void DoesnotThrowException()
        {
            ExceptionAssert.ExpectException<ApplicationException>(() => { }, string.Empty);
        }

        [TestMethod]
        public void ThrowException()
        {
            ExceptionAssert.ExpectException<ApplicationException>(() => { throw new ApplicationException(); }, string.Empty);
        }

        [TestMethod]
        [ExpectedException(typeof(AssertFailedException))]
        public void ThrowExceptionWithIncorrectMessage()
        {
            ExceptionAssert.ExpectException<ApplicationException>(() => { throw new ApplicationException("abc"); }, "cba");
        }

        [TestMethod]
        public void ThrowExceptionWithCorrectMessage()
        {
            ExceptionAssert.ExpectException<ApplicationException>(() => { throw new ApplicationException("abcd"); }, "Bc");
        }

        [TestMethod]
        [ExpectedException(typeof(AssertFailedException))]
        public void ThrowIncorrectException()
        {
            ExceptionAssert.ExpectException<ApplicationException>(() => { throw new SystemException(); }, string.Empty);
        }

        [TestMethod]
        [ExpectedException(typeof(AssertFailedException))]
        public void ThrowDerivedException()
        {
            ExceptionAssert.ExpectException<SystemException>(() => { throw new ArgumentException(); }, string.Empty);
        }

        [TestMethod]
        public void ExpectExceptionWithoutCheckingMessage()
        {
            ExceptionAssert.ExpectException<SystemException>(() => { throw new SystemException("abc"); });
        }

        public static void SimpleMethod() { throw new Exception(); }
        public static int SimpleFunction() { throw new Exception(); }

        [TestMethod]
        public void UseExpressionInsteadOfLambda()
        {
            ExceptionAssert.ExpectException<Exception>(SimpleMethod);
            ExceptionAssert.ExpectException<Exception, int>(SimpleFunction);
        }
    }
}